import json
import logging
import os
import sys
import traceback
import serial
import time
import copy
import binascii
from django.core.serializers.json import DjangoJSONEncoder

# 封装返回数据
from django.http import HttpResponse

from api import models
from api.serial import mould, sample
from api.serial.detect import detect_sample
from detecter import settings
from collections import deque

logger = logging.getLogger('django')

var_queue = deque(maxlen=settings.LATEST_VAR_LENGTH)


# 负样本
# 后来甲方又提出要对负样本分类，然后贴标签
# 未聚类的负样本统一存到rejects.csv里, zxy 修改于 20200103
def rejects_handler(current_mould, detect_result, current_sample, rejects_file, unassigned_file):
    times = 0
    if detect_result:
        logger.info(f'编号为{current_sample.id}的样本品质异常,品质标准差:{detect_result[5]}')
        times = detect_result[5]
    time_cal = time.time()
    # 网络
    # current_mould.bad_increment(frame_length=current_sample.frame_length)
    time_net = time.time()
    # 画像
    current_mould.prod_mould.neg_increment()
    # 样本数据保存到文件中
    current_sample.save_in_file(rejects_file)
    current_sample.save_in_file(unassigned_file)
    time_io = time.time()
    # 将此记录保存到数据库中
    current_sample.keep_sample_record(current_mould.prod_mould, state=0, label=models.Label.get_good_label(),
                                      times=times)
    time_database = time.time()
    # 发送串口机械手分拣与提示报警请求，并在一定时间后复位
    try:
        ser = serial.Serial(settings.PORT_SEND, settings.BAUD_RATE_SEND)
    except Exception:
        # logger.info(f"串口{settings.PORT_SEND}无法打开，请检查设置")
        logger.info(f"串口{settings.PORT_SEND}无法打开，已于2022.07.07取消该COM串口")
        return
    command_open = [b'\x01\x05\x00\x00\xff\x00\x8c\x3a',
                    b'\x01\x05\x00\x01\xff\x00\xdd\xfa']
    command_reset = [b'\x01\x05\x00\x00\x00\x00\xcd\xca',
                     b'\x01\x05\x00\x01\x00\x00\x9c\x0a']
    for command in command_open:
        ser.write(command)
    time.sleep(settings.RESET_TIME_SEND)
    for command in command_reset:
        ser.write(command)
    time_finish = time.time()
    logger.info(f"从计算出负样本距离到完成机械手命令发送共花了: {round(time_finish - time_cal, 2)}s")
    logger.info(f"网络接口(不是备份)耗时: {round(time_net - time_cal, 2)}s")
    logger.info(f"io耗时: {round(time_io - time_net, 2)}s")
    logger.info(f"本地数据库更新耗时: {round(time_database - time_io, 2)}s")
    logger.info(f"从发送命令到机械手反馈耗时: {round(time_finish - time_io, 2)}s")


# def rejects_handler(current_mould, result, sample):
#     if result:
#         logger.warning(f'编号为{sample.id}的样本判定为不良品,品质标准差:{result[5]}')
#     # 对新来的负样本归类到某一标签中
#     neg_file, label = label_figure_handler(current_mould, sample)
#     if not neg_file:
#         neg_file = current_mould.unassigned_file
#     sample.save_in_file(neg_file)
#     current_mould.bad_increment()
#     sample.keep_sample_record(current_mould.prod_mould, state=0, label=label, reason=None, times=result[5])


def undetect_handler(current_mould, current_sample, undetected_file, is_first_sample=False):
    logger.info(f'{current_sample.id}未检测, 视为正样本,临时文件中')
    # 网络
    # if is_first_sample:
    #     current_mould.good_increment()
    # else:
    #     current_mould.bad_increment(frame_length=current_sample.frame_length)
    current_sample.save_in_file(undetected_file)
    current_sample.keep_sample_record(current_mould.prod_mould, state=1, label=models.Label.get_default_label())


# 正样本的处理办法
def accepted_handler(current_mould, detect_result, current_sample, good_file):
    times = 0
    if detect_result:
        logger.info(f'编号为{current_sample.id}的样本品质正常,品质标准差:{detect_result[5]}')
        times = detect_result[5]
    # 网络
    # current_mould.good_increment(current_sample.frame_length)
    # 画像
    current_mould.prod_mould.pos_increment()
    # 样本数据保存到文件中
    current_sample.save_in_file(good_file)
    # 将此记录保存到数据库中
    current_sample.keep_sample_record(current_mould.prod_mould, state=2, label=models.Label.get_good_label(),
                                      times=times)


def first_sample_handler(current_mould, prod_time, id):
    logger.info(f'恢复生产的第一个样本{id}视为正样本,由于样本数据不全故不存入文件中')
    # current_mould.good_increment()


# 给负样本确定标签
def label_figure_handler(current_mould, current_sample):
    # 获取当前模具下的所有标签画像
    neg_figures = models.NegFigure.objects.filter(mould=current_mould.prod_mould)
    # 将该样本拿去与每一个负样本画像去匹配
    # 检测结果归到 detect_result[5] 最小的, 即sigma最小的 的标签中
    if neg_figures:
        similar = sys.maxsize
        similar_figure = None
        for neg_figure in neg_figures:
            figure_result = neg_figure.get_figure_result()
            detect_result = None
            if figure_result.is_result_safe:
                try:
                    detect_result = detect_sample(current_sample, figure_result, cal_seg=False)
                except:
                    s = traceback.format_exc()
                    logger.error(
                        f'{s},预测负样本标签时检测失败,{current_mould.mould_code},{current_sample.id},{neg_figure.label.label_name}')
                if detect_result and detect_result[5]:
                    if similar > detect_result[5]:
                        similar = detect_result[5]
                        similar_figure = neg_figure
        if similar_figure:
            similar_figure.count_increment()
            neg_file = get_file(current_mould.sample_path, similar_figure.label)
            # 贴完标签后，每个标签下的每增加10个样本就更新一次标签画像
            recheck_figure_build(similar_figure, mould.CurrentMould.get_instance().sample_path)
            return neg_file, similar_figure.label
    return None, None


def recheck_figure_build(neg_figure, sample_path):
    if neg_figure:
        if neg_figure.count % settings.NEG_FIGURE_UPDATE_FREQ == 0 and neg_figure.count != 0:
            neg_file = get_file(sample_path, neg_figure.label)
            result = neg_figure.get_figure_result()
            try:
                result.build_portrait(neg_file, all=True)
                neg_figure.save_figure_result(result.dump_to_db())
            except:
                logger.error(f'{neg_figure.label.label_name} 画像建模失败')
                traceback.print_exc()


def get_sample(path, sample_id):
    period_len_list = []
    sample_file = None
    with open(path, 'r') as reader:
        json_list = reader.readlines()
        # 同figure建模一样，选择周期最短那组数据的维数为基准
        for json_str in json_list:
            try:
                json_obj = json.loads(json_str)
            except:
                continue
            id = int(json_obj['id'])
            if id == int(sample_id):
                period_len_list.append(min(len(json_obj['actual_pressure']),
                                           len(json_obj['actual_location']),
                                           len(json_obj['actual_speed'])))
                sample_file = sample.ProductSample(id, frame_list=period_len_list, is_load=True, json_obj=json_obj)
                break
    return sample_file, period_len_list


def restore_sample(origin_file, new_file, sample_id_list, is_cluster=False):
    check_dir_exist(origin_file)
    check_dir_exist(new_file)
    read_num = settings.MAX_CLUSTER_SAMPLES + 10
    f_new = open(new_file, mode='a+')
    cnt = 0
    with open(origin_file, mode='r+') as reader:
        # 聚类函数调用，只读最后的100 + 10个数据
        if is_cluster:
            json_list = reader.readlines()[-read_num:]
            reader.seek(0, 0)
            front_list = reader.readlines()[:-read_num]
        else:
            json_list = reader.readlines()
        save_list = []
        for i in range(len(json_list)):
            json_obj = json.loads(json_list[i])
            if json_obj['id'] in sample_id_list:
                new_str = json_list[i]
                f_new.write(new_str)
                cnt += 1
            else:
                save_list.append(json_list[i])
        logger.info(f"{cnt} 条")
        f_new.close()

        new_list = front_list + save_list if is_cluster else save_list
    with open(origin_file, mode='w') as writer:
        writer.writelines(new_list)


def get_file(path, label):
    check_dir_exist(path)
    if label.pk == models.Label.good_id:
        return mould.CurrentMould.get_instance().good_file
    elif label.pk == models.Label.default_id:
        return mould.CurrentMould.get_instance().unassigned_file
    negLabel_file = os.path.join(path, f'{label.label_name}.csv')
    return negLabel_file


def check_dir_exist(file):
    file_dir = os.path.dirname(file)
    if not os.path.exists(file_dir):
        logger.warning(f'the path {file_dir} is not exists,now make dirs')
        os.makedirs(file_dir)


def bytes2hex(frame_bytes):
    hex_bytes = binascii.b2a_hex(frame_bytes)
    frame_bytes_str = hex_bytes.decode('utf-8').upper()
    return frame_bytes_str


class R:
    def __init__(self):
        self.resp_dict = {'code': 0, 'msg': 'success', 'data': ''}

    @classmethod
    def ok(cls, data=''):
        resp = R()
        resp.resp_dict['data'] = data
        result = json.dumps(resp.resp_dict, cls=DjangoJSONEncoder)  # 使用DjangoJSONEncoder来处理resp.resp_dict中的任何Django对象
        return HttpResponse(result, content_type=settings.CONTENT_TYPE)

    @classmethod
    def error(cls, msg=None):
        resp = R()
        resp.resp_dict['code'] = 500
        if msg:
            resp.resp_dict['msg'] = msg
        else:
            resp.resp_dict['msg'] = '未知异常,请联系管理员'
        result = json.dumps(resp.resp_dict, cls=DjangoJSONEncoder)
        return HttpResponse(result, content_type=settings.CONTENT_TYPE)

    def __str__(self):
        return json.dumps(self.resp_dict, cls=DjangoJSONEncoder)
